package com.tmt.spring.validation;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Created by ThreeManTeam on 2016/3/16.
 */
public class IdCardNumHelper {

    /**
     * 省份编码映射
     */
    private static HashMap princeCode = new HashMap(){{
        put("11", "北京");
        put("12", "天津");
        put("13", "河北");
        put("14", "山西");
        put("15", "内蒙古");

        put("21", "辽宁");
        put("22", "吉林");
        put("23", "黑龙江");

        put("31", "上海");
        put("32", "江苏");
        put("33", "浙江");
        put("34", "安徽");
        put("35", "福建");
        put("36", "江西");
        put("37", "山东");

        put("41", "河南");
        put("42", "湖北");
        put("43", "湖南");
        put("44", "广东");
        put("45", "广西");
        put("46", "海南");

        put("50", "重庆");
        put("51", "四川");
        put("52", "贵州");
        put("53", "云南");
        put("54", "西藏");

        put("61", "陕西");
        put("62", "甘肃");
        put("63", "青海");
        put("64", "宁夏");
        put("65", "新疆");

        put("71", "台湾");

        put("81", "香港");
        put("82", "澳门");

        put("91", "海外");
    }};

    private static Map<Character, Integer> Hk_XyValue = new HashMap<Character, Integer>(){{
        put(' ', 58);
        put('A', 10);
        put('B', 11);
        put('C', 12);
        put('D', 13);
        put('E', 14);
        put('F', 15);
        put('G', 16);
        put('H', 17);
        put('I', 18);
        put('J', 19);
        put('K', 20);
        put('L', 21);
        put('M', 22);
        put('N', 23);
        put('O', 24);
        put('P', 25);
        put('Q', 26);
        put('R', 27);
        put('S', 28);
        put('T', 29);
        put('U', 30);
        put('V', 31);
        put('W', 32);
        put('X', 33);
        put('Y', 34);
        put('Z', 35);
    }};

    private static Map<Character, Integer> Tw_XyValue = new HashMap<Character, Integer>(){{
        put('A', 10);
        put('B', 11);
        put('C', 12);
        put('D', 13);
        put('E', 14);
        put('F', 15);
        put('G', 16);
        put('H', 17);
        put('J', 18);
        put('K', 19);
        put('L', 20);
        put('M', 21);
        put('N', 22);
        put('P', 23);
        put('Q', 24);
        put('R', 25);
        put('S', 26);
        put('T', 27);
        put('U', 28);
        put('V', 29);
        put('X', 30);
        put('Y', 31);
        put('W', 32);
        put('Z', 33);
        put('I', 34);
        put('O', 35);
    }};

    /**
     * 更加严格的检测18位第二身份证号码
     * @param idNum
     * @return
     *  0 ：身份证号码合法；
     * -1 : 格式非法：18位数字，或17位数字加一个字母X，或无效香港身份证
     * -2 ：校验和错误；
     * -3 ：省份编码无效；
     * -4 : 出生日期格式非法；
     * -5 ：出生日期非法（不能是今天或未来的某一天）；
     */
    public static int checkIdCard2(String idNum){
        idNum = idNum.trim().toUpperCase();

        //除最后一位外，其他17位必须是数字，且最后一位只能出现字母‘X’;
        Pattern pattern = Pattern.compile("^\\d{17}([0-9]|X)$");
        if (!pattern.matcher(idNum).matches())
            return -1;

        //检查省份编码是否有效？
        if (!princeCode.containsKey(idNum.substring(0,2)))
            return -3;

        //检测出生日期格式是否正确？
        String strBirthDay = idNum.substring(6, 14);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Date birthday = null;
        try {
            birthday = sdf.parse(strBirthDay);
        }catch (Exception ex){
            return -4;
        }

        //确保出生日期不能是未来的某一天
        try {
            if (birthday.after(sdf.parse(sdf.format(new Date()))))
                return -5;
        }catch (Exception ex){}

        return checkSum(idNum) ? 0 : -2;
    }

    /**
     * 验证第二代身份证号码的校验和
     * @param idNum
     * @return
     */
    private static boolean checkSum(String idNum){

        int[] idCardWi = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}; //将前17位加权因子保存在数组里
        int[] idCardY = new int[]{1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2}; //这是除以11后，可能产生的11位余数、验证码，也保存成数组
        int idCardWiSum = 0; //用来保存前17位各自乖以加权因子后的总和
        for (int i = 0; i < 17; i++) {
            idCardWiSum += Integer.parseInt(idNum.substring(i, i + 1)) * idCardWi[i];
        }

        int idCardMod = idCardWiSum % 11;//计算出校验码所在数组的位置
        String idCardLast = idNum.substring(17);//得到最后一位身份证号码

        //如果等于2，则说明校验码是10，身份证号码最后一位应该是X
        if (idCardMod == 2) {
            return "X".equals(idCardLast);
        }

        //用计算出的验证码与最后一位身份证号码匹配，如果一致，说明通过，否则是无效的身份证号码
        return (Integer.parseInt(idCardLast) == idCardY[idCardMod]);
    }

    /**
     * 验证香港身份证号，样例格式：XYabcdef(z)
     *  身份证号码的「结构」，可以用XYabcdef(z)表示。
     *  「X」可能是「空格」或是一个英文字母，「Y」则必定是英文字母。
     *  「abcdef」代表一个六位数字，而「z」是作为检码之用，它的可能选择是0, 1, 2, ... , 9, A(代表10)。
     *  这些代号的背后，都可配上一个编码值。透过编码值，便可找出 9X+8Y+7a+6b+5c+4d+3e+2f+z 的总和。
     *  该总和特别之处，是必须被11整除。利用这特点，我们便能找出括号内的数字。
     *  X或Y的编码值:
     *  空格 58 I 18 R 27
     *    A 10 J 19 S 28
     *    B 11 K 20 T 29
     *    C 12 L 21 U 30
     *    D 13 M 22 V 31
     *    E 14 N 23 W 32
     *    F 15 O 24 X 33
     *    G 16 P 25 Y 34
     *    H 17 Q 26 Z 35
     * 例如：X354670(?)
     * 9(58)+8(33)+7(3)+6(5)+5(4)+4(6)+3(7)+2(0)+z=902+z
     * 其能被11整除，即X354670(0)是正确的香港身分证号码
     *
     * @param idNum
     * @return
     *  0 ：身份证号码合法；
     * -1 : 数据长度异常；
     * -2 ：数据格式异常；
     * -3 ：数据异常；
     * -4 : 校验和异常；
     */
    public static int checkIdCard_HK(String idNum){
        idNum = idNum.trim().toUpperCase();
        idNum = idNum.replaceAll("[\\(|\\)]", "");

        List<Integer> lenList = Arrays.asList(8,9);
        if (!lenList.contains(idNum.length()))
            return -1;

        if (!idNum.matches("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$")) {
            return -2;
        }

        //不足9位，则在前面添加一个空格补齐9位。
        if (idNum.length() < 9)
            idNum = " " +idNum;

        char[] chars = idNum.toCharArray();

        Integer x = Hk_XyValue.get(chars[0]);
        Integer y = Hk_XyValue.get(chars[1]);
        Integer a = Integer.valueOf(chars[2] + "");
        Integer b = Integer.valueOf(chars[3] + "");
        Integer c = Integer.valueOf(chars[4] + "");
        Integer d = Integer.valueOf(chars[5] + "");
        Integer e = Integer.valueOf(chars[6] + "");
        Integer f = Integer.valueOf(chars[7] + "");
        Integer z = Integer.valueOf(chars[8] + "");

        if (x == null || y == null || a == null || b == null || c == null || e == null || f == null || z == null)
            return -3;

        int total = 9 * x + 8 * y + 7 * a + 6 * b + 5 * c + 4 * d + 3 * e + 2 * f + z;
        return total % 11 == 0 ? 0 : -4;
    }

    /**
     * 校验澳门身份证, 8位（不算括号）
     * @param idNum
     * @return
     */
    public static boolean checkIdCard_AM(String idNum){
        return idNum.matches("^[1|5|7][0-9]{6}\\(?[0-9A-Z]\\)?$");
    }

    /**
     * 校验台湾身份证， 10位
     * @param idNum
     * @return
     */
    public static boolean checkIdCard_TW(String idNum){
        if (idNum.length() < 10)
            return false;

        char start = idNum.charAt(0);
        String mid = idNum.substring(1, 9);
        String end = idNum.substring(9);
        Integer iStart = Tw_XyValue.get(start);
        Integer sum = iStart / 10 + (iStart % 10) * 9;
        char[] chars = mid.toCharArray();
        Integer iFlag = 8;
        for (char c : chars) {
            sum += Integer.valueOf(c + "") * iFlag;
            iFlag--;
        }
        return (sum % 10 == 0 ? 0 : (10 - sum % 10)) == Integer.valueOf(end);
    }

    public static void main(String[] args){
        int result = checkIdCard_HK("X354670(0)");
        System.out.println("result = " + result);

    }

}
